class_name BuffUtil


static func buff_up(ext: Ext, fighter: int, buffs: Array, buff_group: BuffGroupC):
	for buff in buffs:
		buff_up_exec(ext, fighter, buff)
		var fire_type = buff.get(C.BuffAttrKey.fireType)
		if not fire_type:
			buff_group.instant.append(buff)
			continue
		if fire_type == C.BuffFireType.interval:
			var cooldown = buff.get(C.BuffAttrKey.cooldown)
			if not cooldown:
				buff_group.instant.append(buff)
				continue
			buff.next_time = ext.get_next_time(fighter, cooldown)
			buff_group.add_interval_buff(buff)
		elif fire_type == C.BuffFireType.round:
			var cooldown = buff.get(C.BuffAttrKey.cooldown)
			if not cooldown:
				buff_group.instant.append(buff)
				continue
			buff.next_time = ext.get_round_next_time(fighter, cooldown)
			buff_group.add_round_buff(buff)
		else:
			var live_time = buff.get(C.BuffAttrKey.liveTime)
			if live_time > 0:
				buff.next_time = (
					ext.get_round_next_time(fighter, live_time)
					if ext.is_round
					else ext.get_next_time(fighter, live_time)
				)
			elif live_time == 0:
				buff.next_time = -1
				buff_group.instant.append(buff)
			buff_group.add_buff(buff, fire_type, ext.is_round)


static func buff_close(ext: Ext, fighter: int, buff: Buff, buff_group: BuffGroupC, close_type):
	buff_close_exec(ext, fighter, buff)
	buff_group.action_buff = BuffGroupC.ActionBuff.new(close_type, buff)
	pass


static func instant_down(ext: Ext, fighter, buff_group: BuffGroupC):
	var buffs = buff_group.get_instant()
	var b: Buff = buffs.pop_front()
	while b:
		if b.next_time < 0:
			if buff_group.del_buff(b, b.get(C.BuffAttrKey.fireType)):
				buff_close(ext, fighter, b, buff_group, C.BuffCloseType.timeout)
		else:
			buff_down_exec(ext, fighter, b)
			buff_group.action_buff = BuffGroupC.ActionBuff.new(C.ActionBuffType.down, b)
		b = buffs.pop_front()


# exec buff entrance function
static func buff_up_exec(_ext: Ext, fighter: int, buff: Buff):
	var up_func = buff.get(C.BuffAttrKey.upEffectFunc)
	if not up_func:
		return
	var type = buff.get(C.BuffAttrKey.upEffectType)
	var args = buff.get(C.BuffAttrKey.upEffectArgs)
	Funcs.exec(up_func, [buff, fighter, type, args, C.BuffActionType.up])


static func buff_fire_exec(ext, fighter, buff: Buff, set: Set):
	buff.times = +1

	var i = buff.get(C.BuffAttrKey.addup)
	i = i if i else 1
	if buff.times % i != 0:
		return
	if buff.is_over():
		if set:
			set.delete(buff)
			buff_down_exec(ext, fighter, buff)
			return
	var fun = buff.get(C.BuffAttrKey.fireEffectFunc)
	if not fun:
		return
	var type = buff.get(C.BuffAttrKey.fireEffectType)
	var args = buff.get(C.BuffAttrKey.fireEffectArgs)
	Funcs.exec(fun, [buff, fighter, type, args, C.BuffActionType.fire])


static func buff_down_exec(_ext: Ext, fighter: int, buff: Buff):
	var down_func = buff.get(C.BuffAttrKey.downEffectFunc)
	if not down_func:
		return
	var type = buff.get(C.BuffAttrKey.downEffectType)
	var args = buff.get(C.BuffAttrKey.downEffectArgs)
	Funcs.exec(down_func, [buff, fighter, type, args, C.BuffActionType.down])


static func buff_close_exec(_ext: Ext, fighter: int, buff: Buff):
	var close_func = buff.get(C.BuffAttrKey.closeEffectFunc)
	if not close_func:
		return
	var type = buff.get(C.BuffAttrKey.closeEffectType)
	var args = buff.get(C.BuffAttrKey.closeEffectArgs)
	Funcs.exec(close_func, [buff, fighter, type, args, C.BuffActionType.close])


static func fire_buff(ext: Ext, fighter, buff_group: BuffGroupC, fire_type):
	var set = buff_group.get_buffs(fire_type)
	if not set:
		return
	for buff in set.impl:
		if not ext.exec_condition(fighter, buff):
			continue
		if ext.is_round:
			if ext.is_round_cooldown(fighter, buff.cooldown_time):
				continue
			buff.cooldown_time = ext.get_round_next_time(
				fighter, buff.get(C.BuffAttrKey.cooldown) | 0
			)
		else:
			if ext.is_cooldown(fighter, buff.cooldown_time):
				continue
			buff.cooldown_time = ext.get_next_time(fighter, buff.get(C.BuffAttrKey.cooldown) | 0)

		buff_fire(ext, fighter, buff, buff_group, set)


static func interval_buff(ext: Ext, fighter, buff_group: BuffGroupC):
	return time_buff(
		ext,
		fighter,
		buff_group,
		buff_group.get_heap(),
		C.BuffFireType.interval,
		"is_cooldown",
		"get_next_time"
	)


static func round_buff(ext: Ext, fighter, buff_group: BuffGroupC):
	return time_buff(
		ext,
		fighter,
		buff_group,
		buff_group.get_round_heap(),
		C.BuffFireType.round,
		"is_round_cooldown",
		"get_round_next_time"
	)


static func time_buff(
	ext: Ext,
	fighter,
	buff_group: BuffGroupC,
	heap: Heap,
	fire_type,
	is_cooldown: String,
	get_next_time: String
):
	if not heap:
		return false
	var buff = heap.get()
	while buff:
		if ext.call(is_cooldown, fighter, buff.cooldown_time):
			return true
		heap.pop()
		var t = buff.get(C.BuffAttrKey.fireType)
		if t == fire_type:
			if not buff_fire(ext, fighter, buff, buff_group):
				buff.next_time = ext.call(get_next_time, fighter, buff.get(C.BuffAttrKey.cooldown))
				heap.insert(buff)  # if buff still exist, insert it back to heap
		else:
			if buff_group.del_buff(buff, t):
				buff_close(ext, fighter, buff, buff_group, C.BuffCloseType.timeout)
		buff = heap.get()
	return false


# exec buff fire function
static func buff_fire(ext: Ext, fighter: int, buff: Buff, buff_group: BuffGroupC, set = null):
	buff_fire_exec(ext, fighter, buff, set)
	if buff.is_over():
		buff_group.action_buff = BuffGroupC.ActionBuff.new(C.ActionBuffType.down, buff)
		return true
	return false
